package com.xlabs.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import com.xlabs.client.Elements.TextLink;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Conversation view class.
 */
public class Conversation extends Composite {
    /**
     * Conversation Tree.
     */
    private ScrollPanel treeScroll = new ScrollPanel();

    /**
     * ConversationPanel.
     */
    private HorizontalPanel conversationPanel = new HorizontalPanel();

    /**
     * Conversation scroll panel.
     */
    private ScrollPanel conversationScroll = new ScrollPanel();

    /**
     * Initializes Conversation.
     */
    public Conversation()
    {
        this.initWidget(this.conversationPanel);
        
        this.setSize("750", "300");

        this.conversationPanel.add(this.conversationScroll);
        this.conversationPanel.add(this.treeScroll);

        this.treeScroll.setSize("250", "300");
        this.conversationScroll.setSize("500", "300");

        this.treeScroll.addStyleName("ScrollPanel");
        this.conversationScroll.addStyleName("ScrollPanel");
    }

    /**
     * Gets conversation scroll.
     * @return conversation scroll.
     */
    public ScrollPanel getConversationScroll() {
        return conversationScroll;
    }

    /**
     * Gets conversation grid.
     * @return conversation grid.
     */
    public Grid getConversationGrid() {
        return (Grid) conversationScroll.getWidget();
    }

    /**
     * Sets conversationGrid.
     *
     * @param conversationGrid Conversation grid.
     */
    public void setConversationGrid(Grid conversationGrid) {
        conversationGrid.getColumnFormatter().setWidth(0, "350");

        this.conversationScroll.clear();
        this.conversationScroll.add(conversationGrid);

        this.refreshConversationPanel();
    }
    
    /**
     * Shows conversation for the given parent.
     *
     * @param parentEncodedKey parent comment's encoded key.
     */
    public void showConversation(String parentEncodedKey) {
        CommentingServiceAsync commentingService = (CommentingServiceAsync) GWT.create(CommentingService.class);

        commentingService.getConversation(parentEncodedKey, new ConversationAsyncCalBack(this));
    }

    /**
     * Refreshes conversation panel.
     */
    private void refreshConversationPanel() {
        this.conversationPanel.clear();
        this.conversationPanel.add(this.conversationScroll);
        this.conversationPanel.add(this.treeScroll);
    }

    /**
     * Sets conversation tree.
     * @param conversationTree tree of conversation.
     */
    public void setConversationTree(Tree conversationTree) {
        this.treeScroll.clear();

        this.treeScroll.add(conversationTree);

        this.refreshConversationPanel();
    }
}

/**
 * ConversationAsyncCalBack class.
 */
class ConversationAsyncCalBack implements AsyncCallback {
    /**
     * Corresponding conversation object.
     */
    private Conversation conversation;

    public ConversationAsyncCalBack(Conversation conversation) {
        this.conversation = conversation;
    }

    /**
     * On failure.
     *
     * @param throwable exception thrown by server.
     */
    public void onFailure(Throwable throwable) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    /**
     * onSuccess.
     *
     * @param o Object returned from server.
     */
    public void onSuccess(Object o) {
        List<CommentDTO> replies = (List<CommentDTO>) o;

        Grid newGrid = new Grid(replies.size() - 1, 3);

        Tree newTree = new Tree();

        Map<CommentDTO, TreeItem> commentNodeMap = new HashMap<CommentDTO, TreeItem>();

        newTree.addTreeListener(new ConversationTreeListener());

        this.initializeQuestion(replies, newTree, commentNodeMap);

        int index = 0;

        for (CommentDTO reply : replies) {

            //The first reply returned is the question itself
            if (index == 0) {
                index ++;
                continue;
            }

            this.addReplyCell(newGrid, index, reply);

            this.addReplyNode(commentNodeMap, index, reply);

            this.addUserCell(newGrid, index, reply);

            this.addReplyLinkCell(newGrid, index, reply);

            this.formatRowCells(newGrid, index);

            index++;
        }

        this.refreshConversation(newGrid, newTree);
    }

    /**
     * Adds reply cell.
     * @param grid conversation grid.
     * @param index row index.
     * @param reply reply.
     */
    private void addReplyCell(Grid grid, int index, CommentDTO reply) {
        grid.setWidget(index - 1, 0, new Label(reply.getComment()));
    }

    /**
     * Refreshes conversation view.
     * @param newGrid the new grid. 
     * @param newTree the new tree.
     */
    private void refreshConversation(Grid newGrid, Tree newTree) {
        this.conversation.setConversationGrid(newGrid);
        this.conversation.setConversationTree(newTree);
    }

    /**
     * Initializes comment to node map and tree for the question.
     * @param replies list of replies.
     * @param tree conversation tree.
     * @param replyNodeMap reply to node map.
     */
    private void initializeQuestion(List<CommentDTO> replies, Tree tree, Map<CommentDTO, TreeItem> replyNodeMap) {
        TreeItem rootNode = new TreeItem(this.getNodeString(replies.get(0)));
        tree.addItem(rootNode);

        replyNodeMap.put(replies.get(0), rootNode);
    }

    /**
     * Creates reply node.
     * @param replyNodeMap mapping from reply DTO's to nodes.
     * @param index row index.
     * @param reply reply DTO.
     */
    private void addReplyNode(Map<CommentDTO, TreeItem> replyNodeMap, int index, CommentDTO reply) {
        TreeItem commentNode = new TreeItem(this.getNodeString(reply));
        commentNode.setUserObject(new Integer(index - 1));
        replyNodeMap.put(reply, commentNode);

        TreeItem parentNode = replyNodeMap.get(reply.getParent());
        parentNode.addItem(commentNode);
    }

    /**
     * Adds user cell.
     * @param grid grid.
     * @param index row index.
     * @param reply reply DTO.
     */
    private void addUserCell(Grid grid, int index, CommentDTO reply) {
        DateTimeFormat sdf = DateTimeFormat.getFormat("MMM dd");        
        String createdOnText = sdf.format(reply.getCreatedOn());
        
        String cellText = reply.getCreatedBy() + "(" + createdOnText + ")";

        Label userLabel = new Label(cellText);
        grid.setWidget(index - 1, 1, userLabel);
    }

    /**
     * Adds reply cell to the conversation grid.
     * @param grid conversation grid.
     * @param index row index.
     * @param reply reply to be added.
     */
    private void addReplyLinkCell(Grid grid, int index, CommentDTO reply) {
        Label innerReplyLabel = new TextLink("Reply");
        innerReplyLabel.addStyleName("SimpleTextLink");
        innerReplyLabel.addClickListener(new InnerReplyClickListener(index - 1, grid, reply.getEncodedKey()));
        grid.setWidget(index - 1, 2, innerReplyLabel);
    }

    /**
     * Formats cell in the given row.
     * @param grid grid.
     * @param index index of row.
     */
    private void formatRowCells(Grid grid, int index) {
        grid.getCellFormatter().addStyleName(index - 1, 0, "GridCell");
        grid.getCellFormatter().addStyleName(index - 1, 1, "GridCell");
        grid.getCellFormatter().addStyleName(index - 1, 2, "GridCell");
    }

    /**
     * Gets the node string for a given comment.
     * @param comment input comment.
     * @return node string.
     */
    private String getNodeString(CommentDTO comment) {
        DateTimeFormat sdf = DateTimeFormat.getFormat("MMM dd");
        return comment.getCreatedBy() + " " + sdf.format(comment.getCreatedOn());
    }

    /**
     * ReplyListener class.
     */
    private class InnerReplyClickListener implements ClickListener {

        /**
         * index of reply.
         */
        private int index;

        /**
         * Comments grid.
         */
        private Grid commentsGrid;

        /**
         * Question key.
         */
        private String parentReplyKey;

        /**
         * Initializes an instance of ReplyListener.
         *
         * @param index        index of row clicked.
         * @param commentsGrid The comments grid.
         * @param parentReplyKey  key of the question.
         */
        public InnerReplyClickListener(int index, Grid commentsGrid, String parentReplyKey) {
            this.index = index;
            this.commentsGrid = commentsGrid;
            this.parentReplyKey = parentReplyKey;
        }

        /**
         * Onclick.
         *
         * @param widget widget.
         */
        public void onClick(Widget widget) {
            Label commentLAbel = (Label) this.commentsGrid.getWidget(index, 0);
            VerticalPanel replyBox = new VerticalPanel();
            replyBox.add(commentLAbel);

            final TextArea replyArea = new TextArea();
            replyArea.setSize("300px", "100px");
            replyBox.add(replyArea);

            Button submitButton = new Button("Submit", new ClickListener() {
                public void onClick(Widget widget) {
                    CommentingServiceAsync myService = (CommentingServiceAsync) GWT.create(CommentingService.class);
                    myService.postReply(replyArea.getText(), InnerReplyClickListener.this.parentReplyKey, new AsyncCallback() {

                        public void onFailure(Throwable throwable) {
                            replyArea.setText("Failed to post Reply : " + replyArea.getText() + " Because of " + throwable);
                        }

                        public void onSuccess(Object o) {
                            replyArea.setText("Posted Reply : " + replyArea.getText());
                            replyArea.setReadOnly(true);
                        }
                    });
                }
            });

            replyBox.add(submitButton);

            this.commentsGrid.setWidget(index, 0, replyBox);
        }
    }

    /**
     * The ConversationTreeListener class.
     */
    private class ConversationTreeListener implements TreeListener {
        /**
         * OnTreeItemSelected.
         * @param treeItem tree item selected.
         */
        public void onTreeItemSelected(TreeItem treeItem) {
            int index = (Integer) treeItem.getUserObject();

            Conversation conversation = ConversationAsyncCalBack.this.conversation;
            conversation.getConversationScroll().ensureVisible(conversation.getConversationGrid().getWidget(index, 0));
        }

        /**
         * onTreeItemStateChanged method.
         * @param treeItem tree item whose state has changed.
         */
        public void onTreeItemStateChanged(TreeItem treeItem) {
            //To change body of implemented methods use File | Settings | File Templates.
        }
    }
}

